const Koa = require('koa');
const render = require('koa-art-template');
const koaBody = require('koa-body');
const static = require('koa-static');
const session = require('koa-session');
const allRouter = require('./routers/index');

const getUploadFileExt = require('./utils/getUploadFileExt');
const getUploadFileName = require('./utils/getUploadFileName');
const checkDirExists = require('./utils/checkDirExists');

const { port, viewDir, uploadDir } = require('./config/index');
const { formatError, formatRes } = require('./utils/formatLog');

const { checkToken } = require('./utils/jwt');

// const rewriteURL = require('./middleware/rewrite');
// const error = require('./middleware/errHandle');

const app = new Koa();

// 基于 node_music 字符串进行签名的运算, 为的是保证数据不被篡改
app.keys = ['XUJIABIN'];
const store = {
    storage: {},
    set(key, session) {
        this.storage[key] = session;
    },
    get(key) {
        return this.storage[key];
    },
    destroy(key) {
        delete this.storage[key];
    }
}

// 视图渲染模板
render(app, {
    root: viewDir,
    extname: '.html',
    debug: process.env.NODE_ENV !== 'production'
});

// 挂载日志模块
app.use(async (ctx, next) => {
    ctx.util = {
        appLogger: require('./config/log4js').appLogger,
        errorLogger: require("./config/log4js").errorLogger,
        assetsLogger: require('./config/log4js').assetsLogger
    }
    await next();
});

// 使用日志
app.use(async (ctx, next) => {
    // 请求路由是起始时间
    const start = new Date();
    // 判断是否是静态资源文件
    let isAssetsLog = /\/public\//.test(ctx.url);

    await next();
    // 请求路由结束时间
    const resTime = new Date() - start;
    // 写入日志
    if (isAssetsLog) ctx.util.assetsLogger.info(formatRes(ctx, resTime));
    else ctx.util.appLogger.info(formatRes(ctx, resTime));
})

// 路由参数处理中间件
app.use(koaBody({
    multipart: true,
    parsedMethods: ['GET', 'POST', 'PUT', 'DELETE'],
    formidable: {
        maxFieldsSize: 5 * 1024 * 1024,
        uploadDir: uploadDir,
        keepExtensions: true,
        onFileBegin: (name, file) => {
            // 获取文件后缀
            let ext = getUploadFileExt(file.name);
            // 检查目录是否存在
            checkDirExists(uploadDir);
            // 覆盖 file.path
            file.path = `${uploadDir}/${getUploadFileName(ext)}`
        }
    }
}));

// 异常处理
// app.use(error());

// 重写路由
// app.use(rewriteURL(require('./config/rewrite')));

// 静态资源
app.use(static('public'));

// session
app.use(session({ store }, app));

// 将 session 数据挂载到全局
app.use(async (ctx, next) => {
    // ctx.state配置的全局变量我们不仅可以在其他的路由页面使用，还可以在全局模板使用
    ctx.state.user = ctx.session.user; // 模板中直接使用 {{ user }} 即可
    await next();
});

// 判断是否登录（拦截器）
app.use(async (ctx, next) => {
    // 以 /user/login 开头的不需要 验证，直接放行；反之需要验证
    if (ctx.url.startsWith('/user/login')) {
        await next();
        return;
    }

    let token = ctx.request.body.token || ctx.request.query.token;
    // 需要登录
    if (!token) {
        ctx.body = {
            code: false,
            type: 'relogin',
            msg: 'token 不能为空'
        };
        return;
    }
    try {
        let { openid } = await checkToken(token); // 解析 token 获取 openid
        ctx.openid = openid; // 将 openid 挂载到 ctx, 以供后续使用

        await next();
    } catch (error) {
        ctx.body = {
            code: false,
            type: 'relogin',
            msg: 'token失效'
        };
        return;
    }
})

// 路由
app.use(allRouter.routes());

// 错误处理
app.on('error', (err, ctx) => {
    ctx.util.errorLogger.error(formatError((ctx, err)));
    console.error('server error', err, ctx)
});

app.listen(port, () => {
    console.log(`server running at port ${port}`);
});
